/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkClass, ArkMethod, ClassType } from 'arkanalyzer';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects } from '../../Index';
import { ClassMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Rule } from '../../Index';
import { RuleListUtil } from '../../utils/common/DefectsList';
import { IssueReport } from '../../model/Defects';
import { RuleFix } from '../../model/Fix';
import { ClassCategory } from 'arkanalyzer/lib/core/model/ArkClass';
import { CONSTRUCTOR_NAME } from 'arkanalyzer/lib/core/common/TSConst';

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-misused-new.md',
    description: 'Enforce valid definition of new and constructor.'
};

export class NoMisusedNewCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private readonly errorMessageInterface = 'Interfaces cannot be constructed, only classes.';
    private readonly errorMessageClass = 'Class cannot have method named `new`.';

    private createMatcher = (categories: ClassCategory[]): ClassMatcher => ({
        matcherType: MatcherTypes.CLASS,
        category: categories
    });

    private readonly matchers = {
        checkClass: this.createMatcher([ClassCategory.CLASS, ClassCategory.STRUCT]),
        checkTypeLiteral: this.createMatcher([ClassCategory.TYPE_LITERAL]),
        checkInterface: this.createMatcher([ClassCategory.INTERFACE])
    };

    public registerMatchers(): MatcherCallback[] {
        return [
            { matcher: this.matchers.checkClass, callback: this.checkClass },
            { matcher: this.matchers.checkTypeLiteral, callback: this.checkTypeLiteral },
            { matcher: this.matchers.checkInterface, callback: this.checkInterface }
        ];
    }

    public check = (): void => {};

    private checkClass = (arkClass: ArkClass): void => {
        for (const arkMethod of arkClass.getMethods()) {
            //class C {new (): I;} 不告警
            if (arkMethod.getName() === 'new' &&
                (arkMethod.getReturnType() as ClassType).getClassSignature().getClassName() === arkClass.getName()
            ) {
                this.addIssueReport(arkMethod, this.errorMessageClass, undefined);
            }
        }
    };

    private checkTypeLiteral = (arkClass: ArkClass): void => {
        for (const arkMethod of arkClass.getMethods()) {
            if (arkMethod.getName() === CONSTRUCTOR_NAME) {
                this.addIssueReport(arkMethod, this.errorMessageInterface, undefined);
            }
        }
    };

    private checkInterface = (arkClass: ArkClass): void => {
        for (const arkMethod of arkClass.getMethods()) {
            if (
                 //interface I {new (): C;} 不告警
                (arkMethod.getName() === 'construct-signature' &&
                (arkMethod.getReturnType() as ClassType).getClassSignature().getClassName() === arkClass.getName()) ||
                arkMethod.getName() === CONSTRUCTOR_NAME
            ) {
                this.addIssueReport(arkMethod, this.errorMessageInterface, undefined);
            }
        }
    };

    private addIssueReport(arkMethod: ArkMethod, message: string, fix: RuleFix | undefined): Defects {
        const severity = this.rule.alert ?? this.metaData.severity;
        const startLine = arkMethod.getDeclareLines()?.[0] ?? 0;
        const startCol = arkMethod.getDeclareColumns()?.[0] ?? 0;
        const code = arkMethod.getCode() ?? '';
        const endCol = startCol + code.length;

        const defect = new Defects(startLine, startCol, endCol, message, severity, this.rule.ruleId,
            arkMethod.getDeclaringArkFile().getFilePath(), this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, fix));
        RuleListUtil.push(defect);
        return defect;
    }
}